/* * Author: Chris Seguin * * This software has been developed under the copyleft * rules of the GNU General Public License. Please * consult the GNU General Public License for more * details about use and distribution of this software. */ package org.acm.seguin.uml.line; import java.awt.Color; import java.awt.Component; import java.awt.Graphics; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.io.PrintWriter; import java.util.StringTokenizer; import org.acm.seguin.uml.UMLType; /** * SegmentedLine. <P> * * For future builds, it may be worth considering caching the points above * and below the line for an efficiency increase when there are lots of lines * to paint. * *@author Chris Seguin *@created July 28, 1999 */ public class SegmentedLine implements ComponentListener { /** * The set of vertices on the line */ protected Vertex[] vertices; /** * This array is used only during the paint method for drawing polylines */ protected int[] Xs; /** * This array is used only during the paint method for drawing polylines */ protected int[] Ys; private EndPointPanel startPanel; private EndPointPanel endPanel; private int activeVertex; private Segment workingSegment; protected double scalingFactor; private static double SHORT_BACK = 17.32050807568877; // 8.660254037844386; /** * Constructor for the SegmentedLine object * *@param start Panel that the segmented line starts at *@param end End point of the panel */ public SegmentedLine(EndPointPanel start, EndPointPanel end) { // Remember the start and end panels startPanel = start; endPanel = end; scalingFactor = 1.0; // Create and initialize vertices vertices = new Vertex[2]; workingSegment = new Segment(); initEndPoints(); // No vertices are active activeVertex = -1; // Create buffers for drawing polylines Xs = new int[5]; Ys = new int[5]; // Add this as a listener startPanel.addComponentListener(this); endPanel.addComponentListener(this); if (startPanel == endPanel) { Rectangle rect = startPanel.getBounds(); int x = rect.x + rect.width / 2; int y = rect.y + rect.height + 10; insertAt(1, new Vertex(new Point(x, y))); x = rect.x - 10; y = rect.y + rect.height + 10; insertAt(2, new Vertex(new Point(x, y))); x = rect.x - 10; y = rect.y + rect.height / 2; insertAt(3, new Vertex(new Point(x, y))); updateEnd(); } } /** * Determines if both the start and end points are selected * *@return true when both are selected */ public boolean isBothEndsSelected() { return startPanel.isSelected() && endPanel.isSelected(); } /** * Draws the segmented line * *@param g Description of Parameter */ public void paint(Graphics g) { vertices[0].paint(g); for (int ndx = 1; ndx < vertices.length - 1; ndx++) { Xs[ndx - 1] = vertices[ndx].getX(); Ys[ndx - 1] = vertices[ndx].getY(); vertices[ndx].paint(g); } vertices[vertices.length - 1].paint(g); paintCentral(g, Xs, Ys, vertices.length - 2); if (vertices.length == 2) { drawSingleSegment(g); } else { drawStartSegment(g); drawArrow(g); } } /** * Description of the Method * *@param way Description of Parameter */ public void select(boolean way) { for (int ndx = 0; ndx < vertices.length; ndx++) { vertices[ndx].select(way); vertices[ndx].active(false); } } /** * Description of the Method * *@param attempt Description of Parameter *@return Description of the Returned Value */ public boolean hit(Point attempt) { activeVertex = hitVertex(attempt); if (activeVertex == -1) { int insertAt = hitSegment(attempt); if (insertAt == -1) { select(false); return false; } Vertex newOne = new Vertex(attempt); insertAt(insertAt, newOne); select(true); newOne.active(true); activeVertex = insertAt; } else { select(true); vertices[activeVertex].active(true); } return (activeVertex >= 0); } /** * Drag the current vertex * *@param point New location of the current vertex */ public void drag(Point point) { if ((activeVertex > 0) && (activeVertex < vertices.length - 1)) { vertices[activeVertex].move(point); if (activeVertex == 1) { updateStart(); } if (activeVertex == vertices.length - 2) { updateEnd(); } } } /** * The point was dropped */ public void drop() { if ((activeVertex > 0) && (activeVertex < vertices.length - 1)) { vertices[activeVertex].active(false); if (shouldDelete(activeVertex)) { deleteVertex(activeVertex); } activeVertex = -1; } } /** * Description of the Method * *@param cevt Description of Parameter */ public void componentHidden(ComponentEvent cevt) { } /** * Description of the Method * *@param cevt Description of Parameter */ public void componentMoved(ComponentEvent cevt) { Component moved = cevt.getComponent(); if (vertices.length == 2) { updateStart(); updateEnd(); } else if (moved.equals(startPanel)) { updateStart(); } else if (moved.equals(endPanel)) { updateEnd(); } } /** * Description of the Method * *@param cevt Description of Parameter */ public void componentResized(ComponentEvent cevt) { Component moved = cevt.getComponent(); if (moved.equals(startPanel)) { updateStart(); } else if (moved.equals(endPanel)) { updateEnd(); } } /** * Description of the Method * *@param cevt Description of Parameter */ public void componentShown(ComponentEvent cevt) { } /** * Saves a segmented to the output stream * *@param output the output stream */ public void save(PrintWriter output) { output.print("S["); saveStartPanel(output); output.print(","); saveEndPanel(output); output.print("]"); saveVertices(output); output.println(""); } /** * Determines whether this panel matches the two desired end points * *@param start the starting panel to be matched *@param end the ending panel to be matched *@return true if it matches */ public boolean match(EndPointPanel start, EndPointPanel end) { return (start.equals(startPanel) && end.equals(endPanel)); } /** * Loads a segmented line from a buffer * *@param buffer the buffer containing the vertices */ public void load(String buffer) { StringTokenizer tok = new StringTokenizer(buffer, ":"); String countString = tok.nextToken(); int count; try { count = Integer.parseInt(countString); } catch (NumberFormatException nfe) { return; } vertices = new Vertex[count]; String rest = tok.nextToken(); tok = new StringTokenizer(rest, "(),"); for (int ndx = 0; ndx < count; ndx++) { String strX = tok.nextToken(); String strY = tok.nextToken(); try { int x = Integer.parseInt(strX); int y = Integer.parseInt(strY); vertices[ndx] = new Vertex(new Point(x, y)); } catch (NumberFormatException nfe) { vertices[ndx] = new Vertex(new Point(0, 0)); } } int modFiveSize = count / 5 + 1; Xs = new int[modFiveSize * 5]; Ys = new int[modFiveSize * 5]; } /** * Shifts the entire line * *@param x the amount to shift in the x coordinate *@param y the amount to shift in the y coordinate */ public void shift(int x, int y) { for (int ndx = 0; ndx < vertices.length; ndx++) { vertices[ndx].shift(x, y); } } /** * Scales the entire line * *@param value the amount to scale */ public void scale(double value) { for (int ndx = 0; ndx < vertices.length; ndx++) { vertices[ndx].scale(value); } scalingFactor = value; } /** * Determine the point at which the last segment should stop * *@return the point */ protected Point getShortPoint() { int last = vertices.length; workingSegment.reset(vertices[last - 2].getPoint(), vertices[last - 1].getPoint()); double t = workingSegment.findFromEnd(SHORT_BACK * scalingFactor); return workingSegment.getPoint(t); } /** * Determine the point at which the last segment should stop * *@return the point */ protected Point getArrowPointAbove() { int last = vertices.length; workingSegment.reset(vertices[last - 2].getPoint(), vertices[last - 1].getPoint()); double t = workingSegment.findFromEnd(SHORT_BACK * scalingFactor); return workingSegment.aboveLine(t, 10 * scalingFactor); } /** * Determine the point at which the last segment should stop * *@return the point */ protected Point getArrowPointBelow() { int last = vertices.length; workingSegment.reset(vertices[last - 2].getPoint(), vertices[last - 1].getPoint()); double t = workingSegment.findFromEnd(SHORT_BACK * scalingFactor); return workingSegment.belowLine(t, 10 * scalingFactor); } /** * Updates the location of the end vertex */ protected void updateEnd() { int last = vertices.length; Rectangle right = endPanel.getBounds(); workingSegment.reset(vertices[last - 2].getPoint(), right); double t1 = workingSegment.intersect(right); vertices[last - 1].move(workingSegment.getPoint(t1)); } /** * Draws the arrow and the last segment and anything special at the start * *@param g the graphics object */ protected void drawSingleSegment(Graphics g) { drawArrow(g); } /** * Draws anything special at the start * *@param g the graphics object */ protected void drawStartSegment(Graphics g) { Point start = vertices[0].getPoint(); Point end = vertices[1].getPoint(); g.drawLine((int) start.getX(), (int) start.getY(), (int) end.getX(), (int) end.getY()); } /** * Draw central portion of the segmented line * *@param g the graphics object *@param Xs the X coordinates *@param Ys the Y coordinates *@param len the number of coordinates */ protected void paintCentral(Graphics g, int[] Xs, int[] Ys, int len) { g.setColor(Color.black); g.drawPolyline(Xs, Ys, len); } /** * Draws the arrow and the last segment * *@param g the graphics object */ protected void drawArrow(Graphics g) { Point shortPt = getShortPoint(); int last = vertices.length; double X0 = vertices[last - 2].getPoint().getX(); double Y0 = vertices[last - 2].getPoint().getY(); g.drawLine((int) X0, (int) Y0, (int) shortPt.getX(), (int) shortPt.getY()); Point end = vertices[last - 1].getPoint(); Point above = getArrowPointAbove(); Point below = getArrowPointBelow(); Xs[0] = (int) end.getX(); Xs[1] = (int) above.getX(); Xs[2] = (int) below.getX(); Xs[3] = (int) end.getX(); Ys[0] = (int) end.getY(); Ys[1] = (int) above.getY(); Ys[2] = (int) below.getY(); Ys[3] = (int) end.getY(); g.drawPolyline(Xs, Ys, 4); } /** * Saves the start panel * *@param output the output stream */ protected void saveStartPanel(PrintWriter output) { savePanel(output, startPanel); } /** * Saves the end panel * *@param output the output stream */ protected void saveEndPanel(PrintWriter output) { savePanel(output, endPanel); } /** * Saves a panel * *@param output the output stream *@param panel the panel to be saved */ protected void savePanel(PrintWriter output, EndPointPanel panel) { if (panel instanceof UMLType) { output.print(((UMLType) panel).getID()); } else { output.print("???"); } } /** * Saves the vertices * *@param output the output stream */ protected void saveVertices(PrintWriter output) { output.print("{"); output.print(vertices.length); output.print(":"); vertices[0].save(output); for (int ndx = 1; ndx < vertices.length; ndx++) { output.print(","); vertices[ndx].save(output); } output.print("}"); } /** * Initialize the end points of the segmented line. */ private void initEndPoints() { Rectangle left = startPanel.getBounds(); Rectangle right = endPanel.getBounds(); workingSegment.reset(left, right); double t0 = workingSegment.intersect(left); double t1 = workingSegment.intersect(right); vertices[0] = new Vertex(workingSegment.getPoint(t0)); vertices[vertices.length - 1] = new Vertex(workingSegment.getPoint(t1)); } /** * Updates the location the start vertex */ private void updateStart() { Rectangle left = startPanel.getBounds(); workingSegment.reset(left, vertices[1].getPoint()); double t0 = workingSegment.intersect(left); vertices[0].move(workingSegment.getPoint(t0)); } /** * Description of the Method * *@param ndx Description of Parameter *@param newOne Description of Parameter */ private void insertAt(int ndx, Vertex newOne) { Vertex[] newArray = new Vertex[vertices.length + 1]; System.arraycopy(vertices, 0, newArray, 0, ndx); System.arraycopy(vertices, ndx, newArray, ndx + 1, vertices.length - ndx); newArray[ndx] = newOne; vertices = newArray; if (vertices.length > Xs.length) { Xs = new int[Xs.length + 5]; Ys = new int[Ys.length + 5]; } } /** * Have we hit a particular vertex * *@param point the mouse input *@return the index of the vertex that we have hit */ private int hitVertex(Point point) { for (int ndx = 1; ndx < vertices.length - 1; ndx++) { if (vertices[ndx].hit(point)) { return ndx; } } return -1; } /** * Description of the Method * *@param attempt Description of Parameter *@return Description of the Returned Value */ private int hitSegment(Point attempt) { for (int ndx = 0; ndx < vertices.length - 1; ndx++) { Point start = vertices[ndx].getPoint(); Point end = vertices[ndx + 1].getPoint(); workingSegment.reset(start, end); double distance = workingSegment.distanceToPoint(attempt); if ((distance > 0) && (distance < 3)) { return ndx + 1; } } return -1; } /** * Should delete the vertices * *@param active Description of Parameter *@return Description of the Returned Value */ private boolean shouldDelete(int active) { Point pt = vertices[active].getPoint(); // Check if they are in the end rectangle if (startPanel.getBounds().contains(pt) || endPanel.getBounds().contains(pt)) { return true; } // Check the other vertices for (int ndx = 0; ndx < vertices.length; ndx++) { if (ndx != active) { if (vertices[ndx].hit(pt)) { return true; } } } // Don't delete it return false; } /** * Delete a vertex * *@param dead Description of Parameter */ private void deleteVertex(int dead) { Vertex[] newArray = new Vertex[vertices.length - 1]; System.arraycopy(vertices, 0, newArray, 0, dead); System.arraycopy(vertices, dead + 1, newArray, dead, vertices.length - dead - 1); vertices = newArray; } }